<template>
    <div class="flex justify-center items-center  h-100% w-100% box-container">
    <div class="chat-container">
        <div class="chat-header p-l-20px flex ">
            <h3 data-text="AI助手" class="h3-title">AI助手</h3>
        </div>
        <div class="chat-messages" ref="messagesContainer" @scroll="handleScroll">
            <div v-for="(message, index) in messages" :key="index" :class="['message', `${message.sender}-message`]">
                <template v-if="message.sender === 'user'">
                    <div class="position-absolute top-6px right-10px z-1">
                        <img src="@/assets/imgs/user.png" class="icon">
                    </div>
                    <div class="p-r-20px p-t-15px user-message" >
                      {{ message.text }}
                    </div>
                </template>
                <template v-if="message.sender === 'ai'" >
                    <div class="position-absolute top-6px left-10px z-1">
                       <img src="@/assets/imgs/ai.png" class="icon">
                    </div>
                    <div class="p-l-20px p-t-15px ai-message" 
                         v-html="renderMarkdown(message.text)"
                         :class="{ 'typing': message.isTyping }">
                    </div>
                </template>
            </div>
        </div>
        <div class="scrollToBottom" 
             v-if="isScrollToBottom"
             @click="scrollToBottomClick">
            <svg-icon icon-class="down_arrow"></svg-icon>
        </div>
        <div class="input-area position-relative">
            <div class="input-container">
                <el-input
                    v-model="userInput"
                    @keyup.enter="sendMessage"
                    @keydown.enter.prevent
                    :disabled="isLoading"
                    type="textarea"
                    autosize
                    :placeholder="isLoading ? '等待 AI 回复中...' : '输入消息...'"                 
                    class="chat-input"
                />
                <div class="input-actions">
                    <div 
                        class="send-button"
                        :class="{ 'can-send': userInput.trim() && !isLoading }"
                        @click="sendMessage"
                    >
                        <img 
                            :src="isLoading ? loadingIcon : sendIcon" 
                            :class="{ 'rotating': isLoading }"
                        />
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'AiComponents',
})
</script>

<script setup  lang="ts">
import { ref, nextTick } from 'vue'
import { marked } from 'marked'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-light.css';
import loadingIcon from '@/assets/icons/loading.svg'
import sendIcon from '@/assets/icons/send.svg'
import { ElNotification } from 'element-plus'
import useClipboard from "vue-clipboard3";
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark()
const toggleDark = useToggle(isDark)
const { toClipboard } = useClipboard();
const messages = ref<any>([])
const userInput = ref<any>('')
const messagesContainer = ref<any>(null)
const isLoading = ref(false)
const baseApi =  "http://8.137.110.38:3005"
const autoScroll = ref(true) // 是否自动滚动到底部
const isScrollToBottom = ref(false) // 是否滚动到底部
const isUserScrolling = ref(false) // 是否用户正在滚动
const typingSpeed = ref(1) // 控制打字速度
const pendingText = ref('') // 存储待显示的文本
const sendMessage = async (e:any) => {
    if(e.shiftKey) return
    e.preventDefault()
  
    if (!userInput.value.trim() || isLoading.value) return
    isLoading.value = true
    try {
        messages.value.push({
            text: userInput.value,
            sender: 'user'
        }) 
        // 重置滚动状态并滚动到底部
        autoScroll.value = true
        isScrollToBottom.value = false  
        // 等待 DOM 更新后动到底部
        await nextTick()
        const container = messagesContainer.value
        if (container) {
            const targetPosition = container.scrollHeight - container.clientHeight
            container.scrollTo({
                top: targetPosition,
                behavior: 'smooth'
            })
        }
        await handleSearch()
        isLoading.value = false  
    } catch (error) {
        console.log(error)
    }
}
const handleSearch = async () => {
  let buffer = ''
  let scrollDebounceTimer: any = null 
  try {
    const response:any= await fetch(`${baseApi}/rkllm_chat`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'not_required'
    },
    body:JSON.stringify({
      role:'user',
      content:userInput.value
    })
  });
  if(!response.ok){
    throw new Error('请求失败')
  }
  console.log(response,"请求结果");
  
  userInput.value = ''
  const reader = response.body.getReader();// 获取读取
  const decoder = new TextDecoder("utf-8"); // 创建解码器
  // 先添加一个AI消息对象
  messages.value.push({
    text: '',
    sender: 'ai',
    isTyping: true
  });
  // 获取当前消息索引
  const currentMessageIndex = messages.value.length - 1;
  await scrollToBottom()
  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    buffer += decoder.decode(value, { stream: true })
    const lines = buffer.split('\n')
    buffer = lines.pop() || ''
    for (const line of lines) {
      if (line.trim()) {
        try {
          const jsonResponse = JSON.parse(line)
          const delta = jsonResponse.choices[jsonResponse.choices.length - 1].delta
          if (delta && delta.content) {
            const newText = messages.value[currentMessageIndex].text + delta.content
            pendingText.value = newText  
            await typeText(currentMessageIndex, delta.content)
            // 处理滚动
            if (autoScroll.value && !isUserScrolling.value) {
                if (scrollDebounceTimer) clearTimeout(scrollDebounceTimer)
                scrollDebounceTimer = setTimeout(() => {
                    scrollToBottom()
                }, 50)
            }
          }
        } catch (error) {
          console.error('Parse error:', error)
        }
      }
    }
  }
  // 处理最后可能剩余的buffer
  if (buffer.trim()) {
    try {
      const jsonResponse = JSON.parse(buffer);
      const delta = jsonResponse.choices[jsonResponse.choices.length - 1].delta;
      if (delta && delta.content) {
        messages.value[currentMessageIndex].text += delta.content;
        await scrollToBottom()
      }
    } catch (error) {
      console.error('Final parse error:', error);
    }
  }
  } catch (error) {
    console.log(error,"错误");
    ElNotification({
      title: '错误',
      message: '请检查网络设置',
      type: 'error',
    })
  }

}
const debounce = (fn: Function, delay: number) => {
  let timer: any = null
  return function (...args: any) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}
const throttle = (fn: Function, delay: number) => {
  let last = 0
  return function (...args: any) {
    const now = Date.now()
    if (now - last > delay) {
      fn.apply(this, args)
      last = now
    }
  }
}
const handleScroll = (() => {
    let scrollTimer: any = null
    let lastScrollTop = 0
    return throttle((e: Event) => {
        const container = messagesContainer.value
        if (!container) return
        // 获取滚动方向
        const currentScrollTop = container.scrollTop
        const isScrollingUp = currentScrollTop < lastScrollTop // 是否向上滚动
        lastScrollTop = currentScrollTop
        // 计算距离底部的距离
        const distanceToBottom = container.scrollHeight - currentScrollTop - container.clientHeight
        const threshold = 100
        // 标记用户正在滚动
        isUserScrolling.value = true
        // 使用 requestAnimationFrame 优化视觉更新
        requestAnimationFrame(() => {
            // 更新滚动状态
            isScrollToBottom.value = distanceToBottom > threshold
            // 只在向上滚动时更新 autoScroll 状态
            if (isScrollingUp && distanceToBottom > threshold) {
                autoScroll.value = false
            } else if (distanceToBottom < threshold) {
                autoScroll.value = true
            }
            // 清除之前的定时器
            if (scrollTimer) {
                clearTimeout(scrollTimer)
            }
            // 设置新的定时器，在滚动停止后重置状态
            scrollTimer = setTimeout(() => {
                isUserScrolling.value = false
                const finalDistanceToBottom = 
                    container.scrollHeight - container.scrollTop - container.clientHeight
                if (finalDistanceToBottom < threshold) {
                    autoScroll.value = true
                    isScrollToBottom.value = false
                }
            }, 150)
        })
    }, 16)
})()
const scrollToBottomClick = () => {
  autoScroll.value = true
  isScrollToBottom.value = false
  scrollToBottom()
}
// 修改 scrollToBottom 函数
const scrollToBottom = debounce(async () => {
    await nextTick()
    const container = messagesContainer.value
    if (!container || isUserScrolling.value) return
    const targetPosition = container.scrollHeight - container.clientHeight  
    requestAnimationFrame(() => {
        container.scrollTo({
            top: targetPosition,
            behavior: 'auto'
        })
    })
}, 50)
const clearMessage = () => {
    messages.value = []
}
const renderMarkdown = (text: string) => {
  const renderer = new marked.Renderer();
  const escapeHTML = (html: string) => {
    return html
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  };
  renderer.code = (code: any) => {
    const codeText = typeof code === 'object' ? code.raw : code;
    const cleanCode = codeText.replace(/^```\w+\n/, '').replace(/```$/, '');  
    const validLanguage = hljs.getLanguage(code.lang) ? code.lang : 'plaintext';
    let escapedCode = ''
    try {
      const highlighted = hljs.highlight(cleanCode, { language: validLanguage }).value;
      validLanguage === 'html'? escapedCode = escapeHTML(cleanCode):escapedCode = cleanCode.replace(/`/g, '\\`').replace(/\$/g, '\\$');
      return `
        <div class="code-block">
          <div class="code-header">
            <span class="code-lang">${validLanguage}</span>
            <div class="copy-button" data-code="${encodeURIComponent(escapedCode)}" onclick="window.copyCode(this)">
                复制
            </div>
          </div>
          <div class="code-content">
            <pre><code class="hljs ${validLanguage}">${highlighted}</code></pre>
          </div>
        </div>`   
    } catch (error) {
      console.error('Highlighting error:', error);
      return `<pre><code class="hljs">${cleanCode}</code></pre>`;
    }
  };
  renderer.codespan = (code: any) => {
    return `<code class="inline-code">${code.text}</code>`;
  };
  marked.setOptions({
    renderer: renderer
  });
  return marked(text);
}
const copyCode = async (element: HTMLElement) => {
  try {
    const code = decodeURIComponent(element.getAttribute('data-code') || '')
    await toClipboard(code)
    showSuccessNotification()
  } catch (error) {
    handleCopyError(error)
  }
}
const showSuccessNotification = () => {
  ElNotification({
    title: '复制成功',
    message: '代码已复制到剪贴板',
    type: 'success',
  })
}
const handleCopyError = (error: any) => {
  console.error('复制失败:', error)
  ElNotification({
    title: '复制失败',
    message: '请手动复制代码',
    type: 'error',
  })
}
onMounted(()=>{
  (window as any).copyCode = copyCode
})
const typeText = async (messageIndex: number, text: string) => {
  const chars = text.split('')
  for (const char of chars) {
    messages.value[messageIndex].text += char
    await new Promise(resolve => {
      requestAnimationFrame(() => {
        setTimeout(resolve, typingSpeed.value)
      })
    })
  }
}
</script>

<style lang="scss" scoped>
.box-container{
  background: v-bind('isDark ? "linear-gradient(30deg, #2c3e50, #3498db, #2980b9)" : "linear-gradient(30deg, #ffcc00, deeppink, #9c27b0)"');
  animation: hueRotate 20s infinite alternate;
}

.h3-title{
    position: relative;
    word-spacing: 0.2em;
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    color: transparent;
    background-color: v-bind('isDark ? "#409EFF" : "#E8A95B"');
    background-clip: text;
}

.h3-title::after {
    content: attr(data-text);
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 5;
    background-image: linear-gradient(
    120deg,
    transparent 0%,
    transparent 20%,
    white 30%,
    transparent 30.5%,
    transparent 40%,
    rgba(255, 255, 255, 0.3) 50%,
    transparent 60%,
    transparent 65%,
    rgba(255, 255, 255, 0.6) 80%,
    white 85%,
    rgba(255, 255, 255, 0.3) 85.5%,
    transparent 90%,
    transparent 95%,
    rgba(255, 255, 255, 0.3) 97.5%,
    transparent 100%
);
    background-clip: text;
    background-size: 150% 100%;
    background-repeat: no-repeat;
    animation: shine 5s infinite linear;
}

@keyframes shine {
    0% {
        background-position: 50% 0;
    }
    100% {
        background-position: -190% 0;
    }
}

.chat-container {  
    width: 80%;
    height: 90%;
    border: 1px solid v-bind('isDark ? "#363637" : "#ccc"');
    display: flex;
    flex-direction: column;
    background: v-bind('isDark ? "#1e1e1e" : "#fff"');
    border-radius: 8px;
    box-shadow: 0 2px 10px v-bind('isDark ? "rgba(0, 0, 0, 0.3)" : "rgba(0, 0, 0, 0.1)"');

    .chat-messages {
        flex: 1;
        overflow-y: auto;
        padding: 20px;
        scroll-behavior: smooth;
        -webkit-overflow-scrolling: touch;
        
        &::-webkit-scrollbar {
            width: 3px;
            background: v-bind('isDark ? "#363637" : "#ccc"');
        }
        
        will-change: scroll-position;
        backface-visibility: hidden;
        transform: translateZ(0);
    }

    .message {
        margin: 10px 0;
        padding: 12px 16px;
        border-radius: 10px;
        max-width: 80%;
        position: relative;
        font-smooth: always;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        
        .char {
            opacity: 0;
            animation: fadeIn 0.01s forwards;
        }
    }

    @keyframes fadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
    }

    .user-message {
        background: v-bind('isDark ? "#2d2d2d" : "#e3f2fd"');
        margin-left: auto;
    }

    .ai-message {
        background: v-bind('isDark ? "#363636" : "#f5f5f5"');
        margin-right: auto;

        /* &.typing {
            &::after {
                content: '▋';
                display: inline-block;
                vertical-align: bottom;
                animation: blink 1s infinite;
                margin-left: 2px;
            }
        } */
    }

    @keyframes blink {
        0%, 100% { opacity: 1; }
        50% { opacity: 0; }
    }

    .input-area {
        padding: 20px;
        border-top: 1px solid v-bind('isDark ? "#363637" : "rgba(0,0,0,.1)"');
        background: v-bind('isDark ? "#1e1e1e" : "#fff"');

        .input-container {
            position: relative;
            max-width: 48rem;
            margin: 0 auto;
            padding: 8px 15px;
            border: 1px solid v-bind('isDark ? "#363637" : "rgba(0, 0, 0, 0.1)"');
            border-radius: 12px;
            background: v-bind('isDark ? "#2d2d2d" : "#fff"');
          
            .chat-input {
                :deep(.el-textarea__inner) {
                    max-height: 370px;
                    width: calc(100% - 40px);
                    flex: 1;
                    min-height: 24px;
                    margin: 5px 0;
                    transition: all .6s ease;
                    display: block;
                    box-sizing: border-box;
                  
                    padding-right: 40px;
                    color: v-bind('isDark ? "#ffffff" : "#1a2029"');
                    outline: none;
                    border: none;
                    box-shadow: none;
                    resize: none;
                    font-size: 15px;
                    line-height: 25px;
                    font-family: PingFang SC;
                    font-style: normal;
                    caret-color: v-bind('isDark ? "#409eff" : "#2454ff"') !important;
                    
                    &:hover, &:focus {
                        border: none;
                        box-shadow: none;
                        outline: none;
                    }
                    
                    &::-webkit-scrollbar {
                        display: none;
                    }

                    &:disabled {
                        background: transparent;
                        cursor: not-allowed;
                        border: none;
                    }
                }

                :deep(.el-textarea) {
                    border: none;
                    box-shadow: none;
                    
                    &:hover, &:focus {
                        border: none;
                        box-shadow: none;
                    }
                }
            }

            .input-actions {
                position: absolute;
                right: 15px;
                bottom: 10px; // 改为固定距离底部
                z-index: 2;
                
                .send-button {
                    width: 28px;
                    height: 28px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: not-allowed;
                    opacity: 0.5;
                    transition: all 0.3s ease;
                    background: transparent;

                    img {
                        width: 100%;
                        height: 100%;
                    }

                    &.can-send {
                        cursor: pointer;
                        opacity: 1;
                        
                        &:hover {
                            transform: scale(1.05);
                        }
                    }

                    .rotating {
                        animation: rotate 1s linear infinite;
                    }
                }
            }
        }
    }
}

@keyframes rotate {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

.el-button {
    background-color: #868b94;
    height: 100%;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: default;
}

.clear-btn {
    cursor: pointer;
}

.has-text {
    background-color: #1976d2;
    cursor: pointer;
}

.chat-messages{
    scroll-behavior: smooth; // 添加平滑滚动效果
    //过度效果
    transition: all 0.3s ease;
}

.chat-messages::-webkit-scrollbar {
    width: 3px;
    background: #ccc;
}

.icon {
    width: 25px;
    height: 25px;
}


:deep(.ai-message) {
    .code-block {
        margin: 0.5em 0;
        border-radius: 6px;
        .code-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 8px 16px;
            background: v-bind('isDark ? "rgba(255, 255, 255, 0.05)" : "rgba(79, 88, 102, .08)"') !important;
            .code-lang {
                font-size: 15px;
                color: v-bind('isDark ? "#999" : "#999"');
                text-transform: lowercase;
            }       
            .copy-button {
                display: flex;
                align-items: center;
                gap: 6px;
                padding: 4px 8px;
                font-size: 12px;
                color: v-bind('isDark ? "#e0e0e0" : "#333"');
                background: v-bind('isDark ? "#363636" : "#f9fafa"');
                border-radius: 6px;
                cursor: pointer;
                transition: all 0.2s;      
                img{
                    width: 15px;
                    height: 15px;
                    color: v-bind('isDark ? "#333" : "#333"');
                }      
                &:hover{
                    color: v-bind('isDark ? "#409eff" : "#1976d2"');
                }
            }
        }
        .code-content {
            margin: 0;
            padding: 16px;  
            background: v-bind('isDark ? "#2d2d2d" : "#f8f9f6"');
            pre {
                margin: 0;
                background: transparent;
                code {
                    background: transparent;
                    padding: 0;
                    font-size: 16px;
                    line-height: 1.5;
                    color: v-bind('isDark ? "#e0e0e0" : "inherit"');
                    
                    &::-webkit-scrollbar-track {
                        background-color: v-bind('isDark ? "#2d2d2d" : "#f0f0f0"');
                    }

                    &::-webkit-scrollbar-thumb {
                        background-color: v-bind('isDark ? "#4a4a4a" : "#e0e2eb"');
                        border: 2px solid v-bind('isDark ? "#2d2d2d" : "#f0f0f0"');
                        
                        &:hover {
                            background-color: v-bind('isDark ? "#5a5a5a" : "#c6c4ff"');
                        }
                    }
                }
            }
        }
    }
}

:deep(.ai-message) {
    .inline-code {
        background-color: v-bind('isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(175, 184, 193, .2)"');
        border-radius: 6px;
        font-size: 16px;
        margin: 0;
        padding: .2em .4em;
        white-space: break-spaces;
    }
}

.scrollToBottom {
    position: absolute;
    right: 50%;
    transform: translateX(50%);
    bottom: 170px; // 调整位置，确保不会被输入框遮挡
    background: v-bind('isDark ? "#2d2d2d" : "#ffffff"');
    border: 1px solid v-bind('isDark ? "#363637" : "#e0e0e0"');
    border-radius: 50%;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 2px 8px v-bind('isDark ? "rgba(0, 0, 0, 0.3)" : "rgba(0, 0, 0, 0.15)"');
    transition: all 0.3s ease;
    z-index: 1000;

    &:hover {
        background: v-bind('isDark ? "#363636" : "#f5f5f5"');
        transform: translateX(50%) translateY(-2px);
    }

    svg-icon {
        font-size: 20px;
        color: v-bind('isDark ? "#e0e0e0" : "#666"');
    }
}

// 添加过渡动画
.scrollToBottom-enter-active,
.scrollToBottom-leave-active {
    transition: opacity 0.3s, transform 0.3s;
}

.scrollToBottom-enter-from,
.scrollToBottom-leave-to {
    opacity: 0;
    transform: translateX(50%) translateY(20px);
}

@keyframes hueRotate {
    100% {
        filter: hue-rotate(360deg);
    }
}
</style>